/**
 * Copyright (C) 2010 - 2013 Harry Glasgow
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.jaden.ui;

import com.googlecode.jaden.common.config.IOConfiguration;
import com.googlecode.jaden.common.config.ProjectConfiguration;
import com.googlecode.jaden.common.other.JadenException;
import com.googlecode.jaden.common.other.ReopenMap;
import com.googlecode.jaden.common.other.Utils;
import com.googlecode.jaden.engine.Layer;
import static com.googlecode.jaden.ui.Constants.*;
import com.googlecode.jaden.ui.dialog.*;
import com.googlecode.jaden.ui.dialog.automatedtraining.AutomatedTrainingConfigDialog;
import com.googlecode.jaden.ui.dialog.automatedtraining.AutomatedTrainingDialog;
import com.googlecode.jaden.ui.dialog.automatedtraining.TrainingMode;
import com.googlecode.jaden.ui.dialog.automatic.AutomaticProjectCreateDialog;
import com.googlecode.jaden.ui.dialog.projectdefinition.EditProjectDefinitionDialog;
import com.googlecode.jaden.ui.dialog.manualtraining.ManualTrainingDialog;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class UI extends JFrame {

    private static final Logger logger = Logger.getLogger(UI.class.getName());

    private final Preferences preferences;
    private final Actions actions;
    private ProjectConfiguration project;
    private final com.googlecode.jaden.ui.MenuBar menuBar;
    private final JFileChooser chooser;
    private final ReopenMap reopenMap = new ReopenMap();

    public UI() throws HeadlessException {

        preferences = Preferences.userNodeForPackage(UI.class);

        if (preferences.getBoolean(UI_SHOW_SPLASH, UI_SHOW_SPLASH_DEFAULT)) {
            new Splash(this).setVisible(true);
        }

        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                tryToExit();
            }
        });

        int x = preferences.getInt(UI_X, UI_X_DEFAULT);
        int y = preferences.getInt(UI_Y, UI_Y_DEFAULT);
        int w = preferences.getInt(UI_WIDTH, UI_W_DEFAULT);
        int h = preferences.getInt(UI_HEIGHT, UI_H_DEFAULT);
        setLocation(x, y);
        setSize(w, h);

        int e = preferences.getInt(UI_EXTENDED, UI_E_DEFAULT);
        setExtendedState(e);

        updateTitle();

        actions = new Actions(this);

        menuBar = new com.googlecode.jaden.ui.MenuBar(this, actions);
        setJMenuBar(menuBar);
        if (preferences.getBoolean(UI_SHOW_TOOLBAR, UI_SHOW_TOOLBAR_DEFAULT)) {
            add(new ToolBar(actions), BorderLayout.PAGE_START);
        }

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            setDefaultLookAndFeelDecorated(true);
        } catch (Exception e1) {
            Utils.handleException(e1);
        }

        setIconImage(UiUtils.getImage("images/J.png"));

        chooser = new JFileChooser();
        loadProjectDirectory(chooser);
        chooser.addChoosableFileFilter(new JadenFileFilter());

        if (preferences.getBoolean(UI_LOAD_LAST, UI_LOAD_LAST_DEFAULT)) {
            loadLastProject();
        }

        if (preferences.getBoolean(UI_FIRST_SHOW, UI_FIRST_SHOW_DEFAULT)) {
            preferences.putBoolean(UI_FIRST_SHOW, false);
            pack();
            setSize(new Dimension(w, getHeight()));
        }

        for (int i = 0; i < reopenMap.getMaxSize(); i++) {
            String s = preferences.get(UI_REOPEN + i, "");
            if (s.length() != 0) {
                File f = new File(s);
                if (f.exists()) {
                    reopenMap.setFile((long) i, f.getAbsoluteFile());
                }
            }
        }

        menuBar.updateReopenList(reopenMap);

        logger.info("==== Started UI ====");
    }

    private void loadProjectDirectory(JFileChooser chooser) {
        String s = preferences.get(UI_CHOOSER_PROJECT_DIRECTORY, UI_CHOOSER_PROJECT_DIRECTORY_DEFAULT);
        if (!Utils.isBlankOrNull(s)) {
            chooser.setCurrentDirectory(new File(s));
        }
    }

    public void loadDataDirectory(JFileChooser chooser) {
        String s = preferences.get(UI_CHOOSER_DATA_DIRECTORY, UI_CHOOSER_DATA_DIRECTORY_DEFAULT);
        if (!Utils.isBlankOrNull(s)) {
            chooser.setCurrentDirectory(new File(s));
        }
    }

    public void setMaxReopenFiles(int max) {
        reopenMap.setMaxSize(max);
        menuBar.updateReopenList(reopenMap);
    }

    protected void tryToExit() {

        if (project != null && project.isModified() && preferences.getBoolean(
                UI_WARN_MODIFIED, UI_WARN_MODIFIED_DEFAULT)) {
            if (!askToSave()) {
                return;
            }
        }

        if (preferences.getBoolean(UI_CONFIRM_ON_CLOSE, UI_CONFIRM_ON_CLOSE_DEFAULT)) {
            if(!confirmOnClose()) {
                return;
            }
        }

        try {

            int e = getExtendedState();
            if (e == NORMAL) {
                preferences.putInt(UI_X, getX());
                preferences.putInt(UI_Y, getY());
                preferences.putInt(UI_WIDTH, getWidth());
                preferences.putInt(UI_HEIGHT, getHeight());
            }
            preferences.putInt(UI_EXTENDED, getExtendedState());

            for (int i = 0; i < reopenMap.getMaxSize(); i++) {
                if (i < reopenMap.getSize()) {
                    File file = reopenMap.getFile(i);
                    preferences.put(UI_REOPEN + i, file.getAbsolutePath());
                } else {
                    preferences.put(UI_REOPEN + i, "");
                }
            }

            preferences.flush();
        } catch (BackingStoreException e1) {
            Utils.handleException(e1);
        }

        logger.info("==== Stopped UI ====");

        System.exit(0);
    }

    private boolean confirmOnClose() {
        int i = JOptionPane.showConfirmDialog(this, Utils.translate(LABEL_CONFIRM_CLOSE_TEXT),
                Utils.translate(LABEL_CONFIRM_CLOSE_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        return (i == JOptionPane.YES_OPTION);
    }

    private void loadLastProject() {
        String lastFileName = preferences.get(UI_LAST_FILE, "");
        if (Utils.isBlankOrNull(lastFileName)) {
            logger.info("No last project to load");
        } else {
            logger.info("Loading last project " + lastFileName);
        }
        if (lastFileName.length() > 0) {
            File lastFile = new File(lastFileName);
            if (lastFile.exists() && lastFile.canRead()) {
                BufferedReader br = null;
                try {
                    br = new BufferedReader(new FileReader(lastFile));
                    String s;
                    StringBuilder sb = new StringBuilder();
                    while((s = br.readLine()) != null) {
                        sb.append(s);
                    }
                    XStream xStream = new XStream(new DomDriver());
                    Object o = xStream.fromXML(sb.toString());
                    project = (ProjectConfiguration) o;
                    if (project != null) {
                        project.setModified(false);
                        project.setCurrentFile(lastFile);
                        updateTitle();
                        actions.setHaveProject(true);
                        // This is probably where the user is going to want to be.
                        chooser.setCurrentDirectory(lastFile.getParentFile());
                        logger.info("Loaded last project " + lastFileName);
                    }
                } catch (Exception e) {
                    Utils.handleException(e);
                } finally {
                    if (br != null) {
                        try {
                            br.close();
                        } catch (IOException e) {
                            // Don't care.
                        }
                    }
                }
            }
        }
    }

    public void doNewManual() {
        if (project != null && project.isModified()) {
            if (!askToSave()) {
                return;
            }
        }

        project = new ProjectConfiguration();
        project.setName(Utils.translate(LABEL_UNTITLED_TEXT));
        actions.setHaveProject(true);
        updateTitle();
        new EditProjectDetailsDialog(this, preferences, project, true).setVisible(true);
    }

    public void doCloseProject() {
        if (project != null && project.isModified()) {
            if (!askToSave()) {
                return;
            }
        }

        project = null;
        actions.setHaveProject(false);
        updateTitle();
        updateLastFile(null);
    }

    private boolean askToSave() {
        int i = JOptionPane.showOptionDialog(this,
                Utils.translate(MESSAGE_SAVE_CHANGES, project.getName()),
                Utils.translate(LABEL_UI_TITLE), JOptionPane.YES_NO_CANCEL_OPTION,
                JOptionPane.QUESTION_MESSAGE, null, new Object[]{
                        Utils.translate(LABEL_YES_TEXT),
                        Utils.translate(LABEL_NO_TEXT),
                        Utils.translate(LABEL_CANCEL_TEXT)}, 0);
        if (i == 0) {
            return doSaveProject();
        } else if (i == 1) {
            // Proceed.
            return true;
        }

        // Cancel. Do not proceed.
        return false;
    }

    public void updateProjectDetails(String name, String description) {
        project.setName(name);
        project.setDescription(description);
        project.setModified(true);
        actions.setProjectModified(true);
        updateTitle();
    }

    private void updateTitle() {
        if (project != null && project.getName().trim().length() > 0) {
            setTitle(project.getName() + (project.isModified() ? "*" : "") +
                    " - " + Utils.translate(LABEL_UI_TITLE) + ' ' + DIRECT_VERSION);
        } else {
            setTitle(Utils.translate(LABEL_UI_TITLE) + ' ' + DIRECT_VERSION);
        }
    }

    public boolean doSaveProject() {
        if (project.getCurrentFile() == null) {
            return doSaveProjectAs();
        }
        return actuallySave(project.getCurrentFile());
    }

    public boolean doSaveProjectAs() {
        int i = chooser.showSaveDialog(this);
        if (i == JFileChooser.APPROVE_OPTION) {
            storeProjectDirectory(chooser);
            File selectedFile = chooser.getSelectedFile();

            if (!selectedFile.getName().contains(".")) {
                File currentFile = selectedFile;
                selectedFile = new File(selectedFile.getParent(), selectedFile.getName() + '.' + DIRECT_JPF_FILE_EXTENSION);
                logger.info("Renaming file " + currentFile.getName() + " to " + selectedFile.getName());
            }

            // Overwriting an existing file?
            if (project.getCurrentFile() == null ||
                    !project.getCurrentFile().equals(selectedFile)) {
                if (selectedFile.exists()) {
                    int j = JOptionPane.showOptionDialog(this,
                            Utils.translate(MESSAGE_OVERWRITE,
                                    selectedFile.getName()),
                            Utils.translate(LABEL_UI_TITLE),
                            JOptionPane.YES_NO_OPTION,
                            JOptionPane.QUESTION_MESSAGE, null, new Object[]{
                                    Utils.translate(LABEL_YES_TEXT),
                                    Utils.translate(LABEL_NO_TEXT)}, 0);
                    if (j == 1) {
                        return false;
                    }
                }
            }
            project.setCurrentFile(selectedFile);
            return actuallySave(selectedFile);
        } else {
            return false;
        }
    }

    private boolean actuallySave(File file) {
        XStream xStream = new XStream(new DomDriver());
        String s = xStream.toXML(project);
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileOutputStream(file));
            pw.write(s);
            pw.flush();
            pw.close();
            project.setModified(false);
            project.compress();
            actions.setProjectModified(false);
            updateTitle();
            updateLastFile(file);
            reopenMap.setFile(new Date().getTime(), file);
            return true;
        } catch (IOException e) {
            Utils.handleException(e);
            return false;
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
    }

    public void doOpenProject() {

        if (project != null && project.isModified()) {
            if (!askToSave()) {
                return;
            }
        }

        int i = chooser.showOpenDialog(this);
        if (i == JFileChooser.APPROVE_OPTION) {
            storeProjectDirectory(chooser);
            File selectedFile = chooser.getSelectedFile();
            doOpen(selectedFile);
        }
    }

    private void storeProjectDirectory(JFileChooser chooser) {
        preferences.put(UI_CHOOSER_PROJECT_DIRECTORY, chooser.getSelectedFile().getParentFile().getAbsolutePath());
        try {
            preferences.flush();
        } catch (BackingStoreException e1) {
            Utils.handleException(e1);
        }
    }

    public void storeDataDirectory(JFileChooser chooser) {
        preferences.put(UI_CHOOSER_DATA_DIRECTORY, chooser.getSelectedFile().getParentFile().getAbsolutePath());
        try {
            preferences.flush();
        } catch (BackingStoreException e1) {
            Utils.handleException(e1);
        }
    }

    public void doReopen(int key) {
        File file = reopenMap.getFile(key);
        if (file.exists() && file.canRead()) {
            doOpen(file);
        }
    }

    private void doOpen(File selectedFile) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(selectedFile));
            String s;
            StringBuilder sb = new StringBuilder();
            while ((s = br.readLine()) != null) {
                sb.append(s);
            }
            XStream xStream = new XStream(new DomDriver());
            Object o = xStream.fromXML(sb.toString());
            project = (ProjectConfiguration) o;
            if (project != null) {
                project.setModified(false);
                project.setCurrentFile(selectedFile);
                updateTitle();
                updateLastFile(selectedFile);
                actions.setHaveProject(true);
                reopenMap.setFile(new Date().getTime(), selectedFile);
                menuBar.updateReopenList(reopenMap);
            }
        } catch (Exception e) {
            handleFileOpenException(e, selectedFile);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    }

    private void handleFileOpenException(Exception e, File selectedFile) {
        Utils.handleException(e);
        if (selectedFile.getName().endsWith("." + Constants.DIRECT_JPF_FILE_EXTENSION)) {
            JOptionPane.showMessageDialog(this, Utils.translate(Constants.ERROR_FILE_OPEN_PROBLEM, selectedFile.getName()));
        } else {
            JOptionPane.showMessageDialog(this, Utils.translate(Constants.ERROR_FILE_OPEN_PROBLEM_NON_JPF, selectedFile.getName()));
        }
    }

    private void updateLastFile(File file) {
        if (file == null) {
            preferences.put(UI_LAST_FILE, "");
        } else {
            preferences.put(UI_LAST_FILE, file.getAbsolutePath());
        }
        try {
            preferences.flush();
        } catch (BackingStoreException e1) {
            Utils.handleException(e1);
        }
    }

    public void doEditProjectDetails() {
        new EditProjectDetailsDialog(this, preferences, project, false).setVisible(true);
    }

    public void doEditProjectDefinition() {
        new EditProjectDefinitionDialog(this, preferences, project).setVisible(true);
    }

    public void updateProjectDefinition(IOConfiguration[] inputConfigurations,
                                        IOConfiguration outputConfiguration) {
        project.setInputConfigurations(inputConfigurations);
        project.setOutputConfiguration(outputConfiguration);
        project.putBestLayers(new HashMap<Integer, Layer>());
        project.setModified(true);

        // Set default column mappings.
        int numberOfInputs = inputConfigurations.length;
        project.setInputColumnMappings(new int[numberOfInputs]);
        for (int i = 0; i < numberOfInputs; i++) {
            project.getInputColumnMappings()[i] = i;
        }
        project.setOutputColumnMapping(numberOfInputs);

        actions.setProjectModified(true);
        updateTitle();
    }

    public void doPreferences() {
        new PreferencesDialog(this, preferences).setVisible(true);
    }

    public void doEditTrainingFiles() {
        new EditTrainingFilesDialog(this, preferences, project).setVisible(true);
    }

    public void updateTrainingFiles(String trainingFileName,
                                    boolean useTrainingForValidation,
                                    String validationFileName,
                                    int validationPercentage,
                                    boolean useTrainingForTesting,
                                    String testingFileName, int testingPercentage,
                                    int firstLine, boolean scanToEnd,
                                    int lastLine, boolean spaceDelimiter,
                                    boolean tabDelimiter,
                                    String delimiter, String decimalSeparator, boolean useThousandsSeprator,
                                    String thousandsSeparator, boolean useStringLimiter, String stringLimiter,
                                    boolean randomizeData,
                                    boolean coalesceDuplicateColumnSeparators) {
        project.setTrainingFilePath(trainingFileName);
        project.setValidationFromTrainingFile(useTrainingForValidation);
        project.setValidationFilePath(validationFileName);
        project.setValidationDataPercentage(validationPercentage);
        project.setTestingFromTrainingFile(useTrainingForTesting);
        project.setTestingFilePath(testingFileName);
        project.setTestingDataPercentage(testingPercentage);
        project.setFirstLine(firstLine);
        project.setScanToEnd(scanToEnd);
        project.setLastLine(lastLine);
        project.setCoalesceDuplicateColumnSeparators(coalesceDuplicateColumnSeparators);
        if (spaceDelimiter) {
            project.setColumnSeparator(" ");
        } else if (tabDelimiter) {
            project.setColumnSeparator("\t");
        } else {
            project.setColumnSeparator(delimiter);
        }
        project.setDecimalSeparator(decimalSeparator);
        project.setUseThousandsSeparator(useThousandsSeprator);
        project.setThousandsSeparator(thousandsSeparator);
        project.setUseStringLimiter(useStringLimiter);
        project.setStringLimiter(stringLimiter);
        project.setModified(true);
        actions.setProjectModified(true);
        project.setRandomizeData(randomizeData);
        updateTitle();
    }

    public void doEditFileMappings() {
        new EditFileMappingsDialog(this, preferences, project).setVisible(true);
    }

    public void updateMappings(int[] inputMappings, int outputMapping) {
        project.setInputColumnMappings(inputMappings);
        project.setOutputColumnMapping(outputMapping);
        project.setModified(true);
        actions.setProjectModified(true);
        updateTitle();
    }

    public void doManualTraining() {

        String error = project.getValidationError();
        UiUtils.setWaitCursor(rootPane);
        if (Utils.isBlankOrNull(error)) {
            ManualTrainingDialog dialog = new ManualTrainingDialog(this, preferences, project);
            UiUtils.clearCursor(rootPane);
            dialog.setVisible(true);
        } else {
            UiUtils.clearCursor(rootPane);
            JOptionPane.showMessageDialog(this, error);
        }
    }

    public void updateManualTraining(double manualLearningRate, double manualMomentum, int manualEpochs, 
                                     Map<Integer, Layer> bestLayers) {
        project.setManualLearningRate(manualLearningRate);
        project.setManualMomentum(manualMomentum);
        project.setManualEpochs(manualEpochs);
        project.putBestLayers(bestLayers);
        project.setModified(true);
        actions.setProjectModified(true);
        updateTitle();
    }

    public void doClearReopen() {
        reopenMap.clear();
        menuBar.updateReopenList(reopenMap);
    }

    public void doAbout() {
        AboutDialog dialog = new AboutDialog(this);
        dialog.setVisible(true);
    }

    public void doNewAutomatic() {
        if (project != null && project.isModified()) {
            if (!askToSave()) {
                return;
            }
        }

        AutomaticProjectCreateDialog dialog = new AutomaticProjectCreateDialog(this, preferences);
        dialog.setVisible(true);
    }

    public void setProject(ProjectConfiguration project) {
        this.project = project;
        actions.setHaveProject(true);
        project.setModified(true);
        actions.setProjectModified(true);
        updateTitle();
    }

    public void configureAutomatedTraining() {
        String error = project.getValidationError();
        UiUtils.setWaitCursor(rootPane);
        if (Utils.isBlankOrNull(error)) {
            AutomatedTrainingConfigDialog dialog = new AutomatedTrainingConfigDialog(this, project, preferences);
            UiUtils.clearCursor(rootPane);
            dialog.setVisible(true);
        } else {
            UiUtils.clearCursor(rootPane);
            JOptionPane.showMessageDialog(this, error);
        }
    }

    public void updateAutomatedTrainingConfig(TrainingMode trainingMode, int rounds, int jogs, double learningRate,
                                              double momentum, int epochs, int minLayers, int maxLayers) {
        project.setTrainingMode(trainingMode);
        project.setRounds(rounds);
        project.setJogs(jogs);
        project.setAutomatedLearningRate(learningRate);
        project.setAutomatedMomentum(momentum);
        project.setAutomatedEpochs(epochs);
        project.setMinimumLayers(minLayers);
        project.setMaximumLayers(maxLayers);
        project.setModified(true);
        actions.setProjectModified(true);
        updateTitle();
    }

    public void updateAutomatedTraining(boolean[] mask, Map<Integer, Layer> layers) throws JadenException {
        project.setBestMask(mask);
        project.putBestLayers(layers);
        project.setModified(true);
        actions.setProjectModified(true);
        updateTitle();
    }

    public void runAutomatedTraining() {
        AutomatedTrainingDialog dialog = new AutomatedTrainingDialog(this, preferences, project);
        dialog.setVisible(true);
    }
}
